import { AlwaysStencilFunc, ConeGeometry, KeepStencilOp, MeshBasicMaterial, NotEqualStencilFunc, ReplaceStencilOp, Vector3 } from "three";
import { Mesh } from "three/src/objects/Mesh"
import MeshUtil from "../../../renderExtension/utils/MeshUtil";
import { App } from "../../../application";
import RouteLine from "./RouteLine";
import Events from "../../../application/constants/Events";
import { Point } from "../../../renderExtension/types/Point";

export default class RouteArrow {
    private app: App;
    private narrowGeometry;
    private narrowMaterial;
    private narrowArray: Array<Mesh> = new Array<Mesh>();
    private scale: number;
    private data: {pointArray: Array<Point>, narrorColor?: string, narrowCountFactor?: number, stencilRef?: number, renderOrder?: number};

    public constructor(data: {pointArray: Array<Point>, narrorColor?: string, narrowCountFactor?: number, stencilRef?: number, renderOrder?: number}) {

        this.app = App.getInstance();
        this.data = data;
        this.data.narrorColor = data.narrorColor || "#FF8F59";
        this.data.narrowCountFactor = data.narrowCountFactor || 8;
        this.data.renderOrder = data.renderOrder || 0;

        this.computeScale();
        this.initNarrowData();
        this.initLineNarrow();

        this.app.on(Events.wheelChange, this.updateLineNarrow.bind(this));
    }

    private computeScale() {
        let cameraPosition = this.app.three.camera.position;
        let camraLookTarget = this.app.camera.lookAtTarget;
        let cameraViewDistance = cameraPosition.distanceTo(camraLookTarget);
        this.scale = cameraViewDistance / 120;
    }

    public initNarrowData() {
        this.narrowGeometry = new ConeGeometry(0.08, 0.2, 8)
        this.narrowGeometry.rotateX(Math.PI * 0.5)
        this.narrowMaterial = new MeshBasicMaterial({ color: this.data.narrorColor });

        if(this.data.stencilRef != undefined){
            this.narrowMaterial.stencilWrite = true;
            this.narrowMaterial.stencilRef = 5;
            this.narrowMaterial.stencilFuncMask = 0xff;
            this.narrowMaterial.stencilFunc = AlwaysStencilFunc; //NotEqualStencilFunc;
            this.narrowMaterial.stencilFail = ReplaceStencilOp;
            this.narrowMaterial.stencilZFail = ReplaceStencilOp;
            this.narrowMaterial.stencilZPass = ReplaceStencilOp;
            this.narrowMaterial.needsUpdate = true;
        }
    }

    public initLineNarrow(isComputeScale = false) {
        let pointArray = this.data.pointArray;
        if (!pointArray) {
            return;
        }
        for (let i = 1; i < pointArray.length; i++) {
            this.buildNarrow(pointArray[i - 1].clone(), pointArray[i].clone(), isComputeScale);
        }
    }

    public buildNarrow(previousPoint, currentPoint, isComputeScale = false) {
        if (isComputeScale) {
            this.computeScale();
        }

        currentPoint = currentPoint.clone().toEPSGWeb()
        previousPoint = previousPoint.clone().toEPSGWeb()

        let curDistance = previousPoint.distanceTo(currentPoint) * 0.98;
        let offset = curDistance / (curDistance / this.scale / this.data.narrowCountFactor);
        let steps = Math.max(Math.floor(curDistance / offset), 1);
        let direction = currentPoint.clone().sub(previousPoint).normalize();
        for (let i = 1; i <= steps; i++) {
            if (steps == 1) {
                offset = curDistance / 2;
            }
            let narrowPoint = previousPoint
                .clone()
                .add(direction.clone().multiplyScalar(i * offset))

            let curNarrowMesh: Mesh = new Mesh(
                this.narrowGeometry,
                this.narrowMaterial
            )
            curNarrowMesh['canNotSelect'] = true //不参与射线拾取
            curNarrowMesh.scale.set(this.scale, this.scale, this.scale)
            curNarrowMesh.position.copy(narrowPoint)
            curNarrowMesh.lookAt(currentPoint)
            curNarrowMesh.renderOrder = this.data.renderOrder;
            this.app.add(curNarrowMesh)
            this.narrowArray.push(curNarrowMesh);
        }
    }

    public removeLineNarrow() {
        this.narrowArray.forEach((element) => {
            MeshUtil.dispose(element)
        })
    }

    public updateLineNarrow() {
        this.computeScale();
        this.removeLineNarrow()
        this.initLineNarrow()
    }
}